package org.eureka.server;


import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.shared.Applications;
import com.netflix.eureka.EurekaServerContextHolder;
import com.netflix.eureka.registry.PeerAwareInstanceRegistry;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceCanceledEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Configuration
@EnableScheduling
public class EurekaInstanceListener implements ApplicationListener<ApplicationEvent> {
	
	private static final Logger LOG = LoggerFactory.getLogger(EurekaInstanceListener.class);

    private Map<String,LostInstance> lostInstanceMap = new ConcurrentHashMap<>();
	
    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        if (applicationEvent instanceof EurekaInstanceCanceledEvent) { // 服务下线
            this.handleEurekaInstanceCanceledEvent((EurekaInstanceCanceledEvent) applicationEvent);
        } else if (applicationEvent instanceof EurekaInstanceRegisteredEvent) { // 服务上线
            this.handleEurekaInstanceRegisteredEvent((EurekaInstanceRegisteredEvent) applicationEvent);
        }
    }

    private void handleEurekaInstanceCanceledEvent(EurekaInstanceCanceledEvent event) {
        LOG.debug("Eureka instance {} canceled", event.getServerId());

        PeerAwareInstanceRegistry registry = EurekaServerContextHolder.getInstance().getServerContext().getRegistry();
        Applications applications = registry.getApplications();
        applications.getRegisteredApplications().forEach((registeredApplication) -> {
            registeredApplication.getInstances().forEach((instance) -> {
                if (instance.getInstanceId().equals(event.getServerId())) {
                    lostInstanceMap.put(instance.getInstanceId(), new LostInstance(instance));
                }
            });
        });
    }

    private void handleEurekaInstanceRegisteredEvent(EurekaInstanceRegisteredEvent event) {
        LOG.debug("Eureka registered {} canceled", event.getInstanceInfo().getInstanceId());

        lostInstanceMap.remove(event.getInstanceInfo().getInstanceId());
    }


    private int notifyIntervals[] = {0,60,120,240,480}; // in seconds
    @Scheduled(cron = "0/30 * * * * ?")
    private void notifyLostInstance() {
        lostInstanceMap.forEach((key, lostInstance) -> {
            if (lostInstance.isNotifyTimeReached()) {
                LOG.info("服务：{}已失效，IP为：{}，失效时间为：{}，请马上重启服务！",
                         lostInstance.getInstanceId(), lostInstance.getIPAddr(), lostInstance.lostTime);

                lostInstance.markNotified();
            }
        });
    }

    private class LostInstance extends InstanceInfo {
        private int currentInterval = 0;
        private Date lostTime;

        LostInstance(InstanceInfo ii) {
            super(ii);
            this.lostTime = new Date();
        }

        boolean isNotifyTimeReached() {
            return new DateTime(this.lostTime).plusSeconds(notifyIntervals[this.currentInterval]).isBeforeNow();
        }

        private void markNotified() {
            this.currentInterval = (++this.currentInterval) % notifyIntervals.length;
        }
    }
}

